﻿using System;
using System.Collections;
using System.Collections.Generic;
using EnvDTE;
using EnvDTE80;

namespace VsTortoiseSVN.Commands
{
    public class ActiveDocumentFindPreviousModification : ActiveDocumentFindModification
    {
        public ActiveDocumentFindPreviousModification(DTE2 application, string name, string caption, string tooltip, int bitmapId)
            : base(application, name, caption, tooltip, bitmapId, ActiveDocumentFindModification.FindKind.Previous)
        {
            PreferredBinding = "Text Editor::Ctrl+Shift+Alt+Up Arrow";
        }
    }

    public class ActiveDocumentFindNextModification : ActiveDocumentFindModification
    {
        public ActiveDocumentFindNextModification(DTE2 application, string name, string caption, string tooltip, int bitmapId)
            : base(application, name, caption, tooltip, bitmapId, ActiveDocumentFindModification.FindKind.Next)
        {
            PreferredBinding = "Text Editor::Ctrl+Shift+Alt+Down Arrow";
        }
    }

    public class ActiveDocumentFindModification : ActiveDocumentBase
    {
        public enum FindKind
        {
            Next,
            Previous
        }

        private FindKind Kind { get; set; }
        private DateTime LastDiffTime { get; set; }
        private string LastDiffFile { get; set; }
        private UnifiedDiffParser Parser { get; set; }

        public ActiveDocumentFindModification(DTE2 application, string name, string caption, string tooltip, int bitmapId, FindKind kind)
            : base(application, name, caption, tooltip, bitmapId)
        {
            Kind = kind;
            Parser = new UnifiedDiffParser();
            LastDiffTime = DateTime.MinValue;
            LastDiffFile = string.Empty;
        }

        public override vsCommandStatus QueryStatus()
        {
            if (Application.HasDocument())
            {
                SvnHelperStatusBools statuses = SvnHelperStatusBools.CreateVersioned();
                statuses.Ignored = false;
                statuses.Normal = false;

                if (SvnHelper.HasLocalTextStatus(FullName, statuses))
                    return vsCommandStatus.vsCommandStatusSupported | vsCommandStatus.vsCommandStatusEnabled;
            }

            return vsCommandStatus.vsCommandStatusSupported;
        }

        public override bool Exec()
        {
            SharpSvn.SvnStatusEventArgs args;
            if (SvnHelper.FileStatus(FullName, out args))
            {
                if (LastDiffTime != args.WorkingCopyInfo.LastChangeTime || LastDiffFile != FullName)
                {
                    System.Diagnostics.Debug.WriteLine("Getting unified diff...");

                    string unifiedDiff;
                    if (SvnHelper.UnifiedLocalDiff(args.FullPath, out unifiedDiff))
                    {
                        Output.Instance.Write(unifiedDiff);

                        Parser.Parse(unifiedDiff);
                        LastDiffTime = args.WorkingCopyInfo.LastChangeTime;
                        LastDiffFile = FullName;
                    }
                }
            }

            return FindModification(Kind);
        }

        private bool FindModification(FindKind kind)
        {
            int line = Parser.FindModification(ActiveLine, Kind == FindKind.Next ? 1 : -1, true);

            if (line != -1)
            {
                TextSelection selection = Application.ActiveDocument.Selection as TextSelection;
                selection.GotoLineCentered(line);
                return true;
            }

            return false;
        }
    }



    internal class UnifiedDiffParser
    {
        internal class DiffEntry
        {
            public int Line;
            public int Length;
        }

        List<DiffEntry> Added = new List<DiffEntry>();

        public UnifiedDiffParser()
        {

        }

        public int FindModification(int line, int direction, bool wrap)
        {
            if (direction < 0)
                return FindPreviousModification(line, wrap);
            else
                return FindNextModification(line, wrap);
        }

        public int FindPreviousModification(int line, bool wrap)
        {
            for (int n = Added.Count - 1; n >= 0; --n)
            {
                if (Added[n].Line < line)
                    return Added[n].Line;
            }

            if (wrap)
                return FindPreviousModification(int.MaxValue, false);

            return -1;
        }

        public int FindNextModification(int line, bool wrap)
        {
            // Try to find a modification beginning
            // at the specified line number.
            for (int n = 0; n < Added.Count; ++n)
            {
                if (Added[n].Line > line)
                    return Added[n].Line;
            }

            // We are here when no modification has been
            // detected. Begin another search from the top.
            if (wrap)
                return FindNextModification(0, false);

            return -1;
        }

        public void Parse(string udiff)
        {
            string[] delimiters = new string[] { "\r\n", "\r", "\n" };
            string[] lines = udiff.Split(delimiters, StringSplitOptions.None);
            int lastDiffLine = 0;

            for (int n = 0; n < lines.Length; ++n)
            {
                lastDiffLine++;
                string line = lines[n];
                if (line.Length == 0)
                    continue;

                switch (line[0])
                {
                    case '@':
                        if (line.StartsWith("@@ "))
                        {
                            int plusIndex = line.LastIndexOf('+');
                            if (plusIndex > -1)
                            {
                                string add = line.Substring(plusIndex + 1, line.Length - plusIndex - 1 - " @@".Length).Trim();
                                string[] addArray = add.Split(',');
                                lastDiffLine = int.Parse(addArray[0]) - 1;
                            }
                        }
                        break;

                    case '+':
                        {
                            if (line.StartsWith("+++"))
                                continue;

                            DiffEntry entry = new DiffEntry();
                            entry.Line = lastDiffLine;
                            entry.Length = 1;
                            Added.Add(entry);
                        }
                        break;

                    case '-':
                        {
                            if (line.StartsWith("---"))
                                continue;

                            lastDiffLine--;
                        }
                        break;

                    default:
                        break;
                }
            }

            if (Output.Instance.Enabled)
            {
                Output.Instance.WriteLine(string.Format("Found {0} modifications", Added.Count));
                for (int n = 1; n < Added.Count; ++n)
                {
                    Output.Instance.WriteLine(string.Format("  entry[{0}].Line {1}", n, Added[n].Line));
                }
            }

            // remove subsequent changes
            if (Added.Count > 0)
            {
                List<DiffEntry> added = new List<DiffEntry>(Added.Count);
                added.Add(Added[0]);
                for (int n = 1; n < Added.Count; ++n)
                {
                    // when the next change is more than one line away
                    // it is not a subsequent change and we have to add it
                    // as separate change again
                    if (Added[n].Line - 1 > Added[n - 1].Line)
                        added.Add(Added[n]);
                    else
                        added[added.Count - 1].Length = added[added.Count - 1].Length + 1;
                }

                Added.Clear();
                Added.AddRange(added);

                if (Output.Instance.Enabled)
                {
                    Output.Instance.WriteLine(string.Format("Collapsed to {0} modifications", Added.Count));
                    for (int n = 1; n < Added.Count; ++n)
                    {
                        Output.Instance.WriteLine(string.Format("  entry[{0}].Line {1}", n, Added[n].Line));
                        Output.Instance.WriteLine(string.Format("  entry[{0}].Length {1}", n, Added[n].Length));
                    }
                }
            }
        }
    }

}
